// --------------------------------------------------------------------------------------------------------------------
// <copyright company="Roman '4ux-nbIx' Novitsky" file="NumericUpDownSlider.cs">
//   Copyright (c) Roman '4ux-nbIx' Novitsky 2010
// </copyright>
// <summary>
//   Defines the NumericUpDownSlider type.
// </summary>
// --------------------------------------------------------------------------------------------------------------------


namespace MtgMC
{
    #region Namespace Imports

    using System;
    using System.Collections.Generic;
    using System.Drawing;
    using System.Globalization;
    using System.Linq;
    using System.Windows.Forms;

    #endregion


    /// <summary>
    /// Describes and manipulates the numeric up down slider.
    /// </summary>
    public class NumericUpDownSlider : UpDownSliderBase<int>
    {
        #region Constants and Fields

        private readonly List<int> _digitValueChangeSteps = new List<int>();
        private readonly List<Label> _labels = new List<Label>();
        private bool _autoScaleEnabled;
        private bool _isInitialized;
        private DateTime _lastClickTime = DateTime.MinValue;
        private Label _mouseDownLabel;
        private int _mouseDownY;
        private string _numberFormat;
        private Size _size;

        #endregion


        #region Constructors and Destructors

        /// <summary>
        /// Initializes a new instance of the <see cref="NumericUpDownSlider"/> class.
        /// </summary>
        public NumericUpDownSlider()
        {
            MaxValue = 10;

            Build();
        }

        #endregion


        #region Properties

        /// <summary>
        /// Gets or sets a value indicating whether auto scale is enabled.
        /// </summary>
        /// <value>
        /// <c>True</c> if auto scale is enabled; otherwise, <c>false</c>.
        /// </value>
        public bool AutoScaleEnabled
        {
            get
            {
                return _autoScaleEnabled;
            }

            set
            {
                if (_autoScaleEnabled == value)
                {
                    return;
                }

                _autoScaleEnabled = value;

                LayoutLabels(true, true);
            }
        }


        /// <summary>
        /// Gets the digit value change steps.
        /// </summary>
        /// <value>
        /// The digit value change steps.
        /// </value>
        public IList<int> DigitValueChangeSteps
        {
            get
            {
                return _digitValueChangeSteps;
            }
        }

        /// <summary>
        /// Gets or sets the font of the text displayed by the control.
        /// </summary>
        /// <value>
        /// The font of the text displayed by the control.
        /// </value>
        /// <returns>
        /// The <see cref="T:System.Drawing.Font"/> to apply to the text displayed by the control.
        /// </returns>
        /// <PermissionSet>
        /// <IPermission class="System.Security.Permissions.EnvironmentPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true"/>
        /// <IPermission class="System.Security.Permissions.FileIOPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true"/>
        /// <IPermission class="System.Security.Permissions.SecurityPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Flags="UnmanagedCode, ControlEvidence"/>
        /// <IPermission class="System.Diagnostics.PerformanceCounterPermission, System, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true"/>
        /// </PermissionSet>
        public override Font Font
        {
            get
            {
                return base.Font;
            }

            set
            {
                base.Font = value;

                Build();
            }
        }

        /// <summary>
        /// Gets or sets the foreground color of the control.
        /// </summary>
        /// <value>
        /// The foreground <see cref="T:System.Drawing.Color"/> of the control.
        /// </value>
        /// <returns>
        /// The foreground <see cref="T:System.Drawing.Color"/> of the control.
        /// </returns>
        /// <PermissionSet>
        /// <IPermission class="System.Security.Permissions.FileIOPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true"/>
        /// </PermissionSet>
        public override Color ForeColor
        {
            get
            {
                return base.ForeColor;
            }

            set
            {
                base.ForeColor = value;

                foreach (Label label in _labels)
                {
                    label.ForeColor = value;
                }
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether this <see cref="NumericUpDownSlider"/> is initialized.
        /// </summary>
        /// <value>
        /// <c>True</c> if this <see cref="NumericUpDownSlider"/> is initialized; otherwise, <c>false</c>.
        /// </value>
        public bool IsInitialized
        {
            get
            {
                return _isInitialized;
            }

            set
            {
                _isInitialized = value;

                Build();
            }
        }

        private bool IsDoubleClick
        {
            get
            {
                bool isDoubleClick = (DateTime.Now - _lastClickTime).TotalSeconds < 2;
                _lastClickTime = DateTime.Now;

                return isDoubleClick;
            }
        }

        #endregion


        #region Methods

        /// <summary>
        /// Adds the specified amount to the value.
        /// </summary>
        /// <param name="value">
        /// The value.
        /// </param>
        /// <param name="amount">
        /// The amount to add.
        /// </param>
        /// <returns>
        /// A new value.
        /// </returns>
        protected override int Add(int value, int amount)
        {
            int newValue = value + amount;

            newValue = GetValidValue(value, newValue, amount);

            return newValue;
        }


        /// <summary>
        /// Determines whether the specified value1 is greater then value2.
        /// </summary>
        /// <param name="value1">
        /// The value1.
        /// </param>
        /// <param name="value2">
        /// The value2.
        /// </param>
        /// <returns>
        /// <c>true</c> if the specified value1 is greater then value2; otherwise, <c>false</c>.
        /// </returns>
        protected override bool IsGreater(int value1, int value2)
        {
            return value1 > value2;
        }


        /// <summary>
        /// Determines whether the specified value1 is lower then value2.
        /// </summary>
        /// <param name="value1">
        /// The value1.
        /// </param>
        /// <param name="value2">
        /// The value2.
        /// </param>
        /// <returns>
        /// <c>true</c> if the specified value1 is lower then value2; otherwise, <c>false</c>.
        /// </returns>
        protected override bool IsLower(int value1, int value2)
        {
            return value1 < value2;
        }


        /// <summary>
        /// Called when max value has changed.
        /// </summary>
        protected override void OnMaxValueChanged()
        {
            Build();
        }


        /// <summary>
        /// Raises the <see cref="E:System.Windows.Forms.Control.MouseDown"/> event.
        /// </summary>
        /// <param name="e">
        /// A <see cref="T:System.Windows.Forms.MouseEventArgs"/> that contains the event data.
        /// </param>
        protected override void OnMouseDown(MouseEventArgs e)
        {
            base.OnMouseDown(e);

            _mouseDownY = e.Y;

            Point screen = PointToScreen(new Point(e.X, e.Y));

            _mouseDownLabel = GetMouseDownTextBox(screen);
        }


        /// <summary>
        /// Raises the <see cref="E:System.Windows.Forms.Control.MouseMove"/> event.
        /// </summary>
        /// <param name="e">
        /// A <see cref="T:System.Windows.Forms.MouseEventArgs"/> that contains the event data.
        /// </param>
        protected override void OnMouseMove(MouseEventArgs e)
        {
            base.OnMouseMove(e);

            if (_mouseDownLabel == null || e.Button == MouseButtons.None)
            {
                return;
            }

            _mouseDownLabel.BackColor = ThumbsDownColor;

            _lastClickTime = DateTime.MinValue;

            int mouseMoveDistance = _mouseDownY - e.Y;

            if (Math.Abs(mouseMoveDistance) <= SlideDistance)
            {
                return;
            }

            _mouseDownY = e.Y;

            int digitIndex = _labels.IndexOf(_mouseDownLabel);
            int delta = GetDefaultDelta(digitIndex);

            if (_digitValueChangeSteps != null && _digitValueChangeSteps.Any())
            {
                if (digitIndex < _digitValueChangeSteps.Count)
                {
                    delta = _digitValueChangeSteps[digitIndex];
                }
            }

            if (mouseMoveDistance < 0)
            {
                DecreaseValue(delta);
            }
            else if (mouseMoveDistance > 0)
            {
                IncreaseValue(delta);
            }

            return;
        }


        /// <summary>
        /// Raises the <see cref="E:System.Windows.Forms.Control.MouseUp"/> event.
        /// </summary>
        /// <param name="e">
        /// A <see cref="T:System.Windows.Forms.MouseEventArgs"/> that contains the event data.
        /// </param>
        protected override void OnMouseUp(MouseEventArgs e)
        {
            base.OnMouseUp(e);

            foreach (Label textBox in _labels)
            {
                textBox.BackColor = ThumbsBackgroundColor;
            }

            _mouseDownLabel = null;

            if (!IsDoubleClick)
            {
                return;
            }

            OnDoubleClick(e);
            return;
        }


        /// <summary>
        /// Called when padding has changed.
        /// </summary>
        protected override void OnPaddingChanged()
        {
            LayoutLabels(true, true);
        }


        /// <summary>
        /// Raises the <see cref="E:System.Windows.Forms.Control.Resize"/> event.
        /// </summary>
        /// <param name="e">
        /// An <see cref="T:System.EventArgs"/> that contains the event data.
        /// </param>
        protected override void OnResize(EventArgs e)
        {
            LayoutLabels(true, false);
        }


        /// <summary>
        /// Called when thumbs background color has changed.
        /// </summary>
        protected override void OnThumbsBackgroundColorChanged()
        {
            foreach (Label label in _labels)
            {
                label.BackColor = ThumbsBackgroundColor;
            }
        }


        /// <summary>
        /// Called when thumbs padding has changed.
        /// </summary>
        protected override void OnThumbsPaddingChanged()
        {
            LayoutLabels(true, true);
        }


        /// <summary>
        /// Called when value has changed.
        /// </summary>
        protected override void OnValueChanged()
        {
            UpdateLabels();
        }


        /// <summary>
        /// Subtracts the specified amount from the value.
        /// </summary>
        /// <param name="value">
        /// The value.
        /// </param>
        /// <param name="amount">
        /// The amount to subtract.
        /// </param>
        /// <returns>
        /// New value.
        /// </returns>
        protected override int Subtract(int value, int amount)
        {
            int newValue = value - amount;

            newValue = GetValidValue(value, newValue, amount);

            return newValue;
        }


        private void Build()
        {
            if (!IsInitialized)
            {
                return;
            }

            string maxValue = MaxValue.ToString(CultureInfo.CurrentCulture);

            _numberFormat = new string('0', maxValue.Length);

            SuspendLayout();

            CreateLabels(maxValue);

            Controls.Clear();

            foreach (Label label in _labels)
            {
                Controls.Add(label);
            }

            LayoutLabels(false, true);

            UpdateLabels();

            ResumeLayout(true);
        }


        private void CreateLabels(string count)
        {
            _labels.Clear();

            for (int i = 0; i < count.Length; i++)
            {
                var label = new Label();
                label.SuspendLayout();

                label.BackColor = ThumbsBackgroundColor;
                label.TextAlign = ContentAlignment.TopCenter;

                label.ResumeLayout(true);
                label.Name = "label" + i;

                _labels.Add(label);
            }
        }


        private int GetDefaultDelta(int digitIndex)
        {
            int indexOf = _labels.Count - (digitIndex + 1);
            var delta = (int)Math.Pow(10, indexOf);

            return delta;
        }


        private Label GetMouseDownTextBox(Point screen)
        {
            foreach (Label textBox in _labels)
            {
                Point client = textBox.PointToClient(screen);

                if (client.X > 0 && client.X < textBox.Width && client.Y > 0 && client.Y < textBox.Height)
                {
                    return textBox;
                }
            }

            return null;
        }


        private int GetValidValue(int originalValue, int newValue, int delta)
        {
            if (newValue > MaxValue)
            {
                int delta1 = MaxValue - originalValue;
                int delta2 = delta - delta1 - 1;

                newValue = MinValue + delta2;
            }

            if (newValue < MinValue)
            {
                int delta1 = originalValue - MinValue;
                int delta2 = delta - delta1 - 1;

                newValue = MaxValue - delta2;
            }

            return newValue;
        }


        private void LayoutLabels(bool suspendLayout, bool forceLayout)
        {
            if (!IsInitialized || !_labels.Any())
            {
                return;
            }

            if (!forceLayout && _size.Width == Width && _size.Height == Height)
            {
                return;
            }

            _size = new Size(Width, Height);

            if (suspendLayout)
            {
                SuspendLayout();
            }

            var font = (Font)Font.Clone();
            SizeF textSize = this.GetTextSize("0", font, 8);

            float horizontalPadding = 0f;
            float verticalPadding = 0f;

            if (AutoScaleEnabled)
            {
                for (int fontSize = 6; fontSize < 200; fontSize++)
                {
                    font = new Font(Font.Name, fontSize, Font.Style);
                    SizeF size = this.GetTextSize("0", font, 8);

                    float maxWidth = (_labels.Count * size.Width) + ((_labels.Count - 1) * ThumbsPadding)
                                     + (Padding * 2);
                    float maxHeight = size.Height + (Padding * 2);

                    if (maxWidth > Width || maxHeight > Height)
                    {
                        break;
                    }

                    textSize = size;
                    horizontalPadding = (Width - maxWidth) / 2;
                    verticalPadding = (Height - maxHeight) / 2;
                }
            }

            int labelX = Padding + (int)horizontalPadding;

            for (int i = 0; i < _labels.Count; i++)
            {
                Label label = _labels[i];

                label.Location = new Point(labelX, (int)verticalPadding + Padding);
                label.Font = font;
                label.Size = new Size((int)textSize.Width, (int)textSize.Height);

                labelX += label.Width + ThumbsPadding;
            }

            Label lastLabel = _labels.Last();

            if (AutoScaleEnabled)
            {
                if (suspendLayout)
                {
                    ResumeLayout(true);
                }

                return;
            }

            int height = lastLabel.Height + (Padding * 2);
            int width = lastLabel.Location.X + lastLabel.Width + Padding;

            Size = new Size(width, height);

            if (suspendLayout)
            {
                ResumeLayout(true);
            }
        }


        private void UpdateLabels()
        {
            string value = Value.ToString(_numberFormat, CultureInfo.CurrentCulture);

            for (int i = 0; i < _labels.Count; i++)
            {
                _labels[i].Text = value[i].ToString();
            }
        }

        #endregion
    }
}